/*
 * main.c
 * 
 * Copyright 2013 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Kamil Cukrowski <kamil@dyzio.pl> wrote this file. As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.
 * ----------------------------------------------------------------------------
 */
#include <ncurses.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#define PSSCRIPT "/usr/local/bin/psscript.sh"
#define WIDTH 80
#define HEIGHT 24
#define FIELD_WIDTH 18
#define FIELD_HEIGHT 3
#define BRIGHTNESS_INTEL_FILE "/sys/class/backlight/intel_backlight/brightness"
#define BRIGHTNESS_ACPI_FILE "/sys/class/backlight/acpi_video0/brightness"

struct menu_s {
	char name[40];
	char* (*name_func)(struct menu_s *, void *); // this function  can  substitute name
	int (*color_func)(struct menu_s *, void *);  // color x to set  aass in wattron(window, COLOR_PAIR(x))); see init_console or smth
	struct menu_s *click_menu; // menu displayed on click
	int (*click_func)(struct menu_s *, void *); // function running on click
	void *private; // priv
};

int startx = 0;
int starty = 0;
const int ONE_LEN = 20;
struct menu_s *menu = NULL;
WINDOW *window;
extern struct menu_s main_menu[];
struct menu_s *services_menu = NULL;

void free_dynamic_menus();
static void safe_exit()
{
	free_dynamic_menus();
	endwin();
        exit(0);
}
static void sig_hup (int param) {
	safe_exit();
}
static int click_exit(struct menu_s *a, void *b) {
	safe_exit();
	return 0;
}

// ------
int run_and_get_number(char *what)
{
	char buff[100];
	FILE *fp;
	
	fp = popen(what, "r");
	if (fp == NULL) {
		printf("Failed to run command %s \n", buff);
		wrefresh(window);
		return -1;
	}
	//while (
	fgets(buff, sizeof(buff), fp);// != NULL )
		//;
	pclose(fp);
	return atoi(buff)+1;
}

// -----------------------------------------------------------------------------------

int multi_color(struct menu_s *menu, void *private)
{
	char buff[ 100 ];
	snprintf(buff, sizeof(buff), PSSCRIPT" checkshort %s ", menu->name);
	return run_and_get_number(buff);
}

int multi_set(struct menu_s *menu, void *private)
{
	char buff[150];
	FILE *fp;
	
	snprintf(buff, sizeof(buff), 
		PSSCRIPT" %s %s 2>/dev/null",
		multi_color(menu, private) == 2 ? "stop" : "start" ,
		menu->name);
	
	wclear(window);
	mvwprintw(window, 0, 0, "");
	wprintw(window, "running:\n%s\n", buff);
	wrefresh(window);
	fp = popen(buff, "r");
	if (fp == NULL) {
		printf("Failed to run command %s \n", buff);
		wrefresh(window);
		return -1;
	}
	while ( fgets(buff, sizeof(buff), fp) != NULL ) {
		wprintw(window, "%s", buff);
		wrefresh(window);
	}
	pclose(fp);
	
	return 0;
}

void free_dynamic_menus() 
{
	free(services_menu);
}
void alloc_dynamic_menus()
{
	const struct menu_s menu_append[] = {
		{ "refresh", NULL, NULL, NULL, NULL, NULL },
		{ "back", NULL, NULL, main_menu, NULL, NULL },
		{ "exit", NULL, NULL, NULL, click_exit, NULL },
		{ "", NULL, NULL, NULL, NULL, NULL },
	};
	const int menu_append_len = sizeof(menu_append)/sizeof(*menu_append);
	FILE *fp;
	char buf[100];
	char buff[2000];
	int buff_len;
	int menu_num;
	int i;
	char *ptr;
	char *buff_ptr = buff;
	const char delim[] = " \n";
	
	snprintf(buff, sizeof(buff), PSSCRIPT" show | sed 's/services: //'");
	
	fp = popen(buff, "r");
	if (fp == NULL) {
		printf("Alloc_dynamic_menus() Failed to run command\n" );
		exit(-1);
	}
	while ( fgets(buf, sizeof(buf), fp) != NULL ) {
		strcpy(buff, buf);
	}
	pclose(fp);
	buff_len = strlen(buff);
	
	if ( buff_len == 0 )
		return;
			
	for(menu_num=1, i=1;i<buff_len;i++)
		if ( buff[i] == ' ' )
			menu_num++;
	if ( menu_num > 20 )
		exit(-1); // paranopia check
	
	services_menu = calloc(menu_num+menu_append_len, sizeof(*services_menu));
	if ( services_menu == NULL )
		goto err_services_menu;
	
	for(i=0; i<menu_num; i++) {
		struct menu_s temp;
		
		for(ptr=NULL;buff_ptr != '\0';buff_ptr++) {
			int tmp;
			if ( ( tmp = strcspn(buff_ptr, delim) ) ) {
				ptr=buff_ptr;
				buff_ptr+=tmp;
				*buff_ptr = '\0';
				buff_ptr++;
				break;
			}
		}
		if ( ptr == NULL )
			break;
		
		if ( strlen(ptr) > 40 )
			goto err_strlen;
		
		strcpy(temp.name, ptr);
		temp.name_func = NULL;
		temp.color_func = multi_color;
		temp.click_menu = services_menu;
		temp.click_func = multi_set;
		temp.private = 0;
		services_menu[i] = temp;
		
	}
	services_menu[i++] = menu_append[0];
	services_menu[i++] = menu_append[1];
	services_menu[i++] = menu_append[2];
	services_menu[i++] = menu_append[3];
		
	for(i=0; main_menu[i].name[0] != '\0'; i++) {
		if ( !strcmp(main_menu[i].name, "services") ) {
			main_menu[i].click_menu = services_menu;
			break;
		}
	}
	
	for(i=0; services_menu[i].name[0] != '\0'; i++) {
		if ( !strcmp(services_menu[i].name, "refresh") ) {
			services_menu[i].click_menu = services_menu;
			break;
		}
	}
	
	return;
	
err_strlen:
	printf("string name too long ");
	free(services_menu);
err_services_menu:
	exit(-1);
}

// -----------------------------------------------------------------------------------

int brightness_set(struct menu_s *menu, void *private)
{
	char mesg[150];
	int n = (int)private;
	if ( n <= 10 ) {
		n *= 212160;
		snprintf(mesg, sizeof(mesg), "echo %d > "BRIGHTNESS_INTEL_FILE, n);
	} else {
		n -= 11;
		snprintf(mesg, sizeof(mesg), "echo %d > "BRIGHTNESS_ACPI_FILE, n);
	}
	system(mesg);
	return 0;
}

int brightness_color(struct menu_s *menu, void *private)
{
	char num[8];
	FILE *fp;
	int n = (int)private;
	if ( n <= 10 ) {
		n *= 212160;
		fp = fopen(BRIGHTNESS_INTEL_FILE, "r");
	} else {
		n -= 11;
		fp = fopen(BRIGHTNESS_ACPI_FILE, "r");
	}
	if (fp == NULL) {
		printf("Failed to open\n");
		wrefresh(window);
		return -1;
	}
	fgets(num, sizeof(num)/sizeof(*num), fp);
	fclose(fp);
	
	return atoi(num) == n ? 2 : 1;
}

struct menu_s brightness_menu[] = {
	{ "0", NULL, brightness_color, brightness_menu, brightness_set, (void*)(0)},
	{ "1", NULL, brightness_color, brightness_menu, brightness_set, (void*)(1)},
	{ "2", NULL, brightness_color, brightness_menu, brightness_set, (void*)(2)},
	{ "3", NULL, brightness_color, brightness_menu, brightness_set, (void*)(3)},
	{ "4", NULL, brightness_color, brightness_menu, brightness_set, (void*)(4)},
	{ "5", NULL, brightness_color, brightness_menu, brightness_set, (void*)(5)},
	{ "6", NULL, brightness_color, brightness_menu, brightness_set, (void*)(6)},
	{ "7", NULL, brightness_color, brightness_menu, brightness_set, (void*)(7)},
	{ "8", NULL, brightness_color, brightness_menu, brightness_set, (void*)(8)},
	{ "9", NULL, brightness_color, brightness_menu, brightness_set, (void*)(9)},
	{ "10", NULL, brightness_color, brightness_menu, brightness_set, (void*)(10)},
	{ "0", NULL, brightness_color, brightness_menu, brightness_set, (void*)(11)},
	{ "1", NULL, brightness_color, brightness_menu, brightness_set, (void*)(12)},
	{ "2", NULL, brightness_color, brightness_menu, brightness_set, (void*)(13)},
	{ "3", NULL, brightness_color, brightness_menu, brightness_set, (void*)(14)},
	{ "4", NULL, brightness_color, brightness_menu, brightness_set, (void*)(15)},
	{ "5", NULL, brightness_color, brightness_menu, brightness_set, (void*)(16)},
	{ "6", NULL, brightness_color, brightness_menu, brightness_set, (void*)(17)},
	{ "7", NULL, brightness_color, brightness_menu, brightness_set, (void*)(18)},
	{ "back", NULL, NULL, main_menu, NULL, NULL },
	{ "", NULL, NULL, NULL, NULL, NULL },
};

// -----------------------------------------------------------------------------------

struct menu_s main_menu[] = {
	{ "main_menu", NULL, NULL, NULL, NULL, NULL },
	{ "services", NULL, NULL, NULL, NULL, NULL }, // gets dynamical initialized in alloc_dynamic_menus 
	{ "brightness", NULL, NULL, brightness_menu, NULL, NULL},
	{ "exit", NULL, NULL, NULL, click_exit, NULL },
	{ "", NULL, NULL, NULL, NULL, NULL },
};

// -----------------------------------------------------------------------------------

#define MENU_LEN_PARANOIA 30

int wait_for_mouse_click(WINDOW *window, int *mouse_x, int *mouse_y)
{
	// return 1 for right mouse  click
	MEVENT event;
	for(;;) {
		if ( wgetch(window) == KEY_MOUSE ) {
			if ( getmouse(&event) == OK ) {
				*mouse_x = event.x;
				*mouse_y = event.y;
				
				if ( event.bstate & 0x04 ) { // right mouse button
					return 1;
				}
			}
		}
	}
}

void render_menu(WINDOW *window, struct menu_s *menu_new)
{
	int i;
	int x = 2;
	int y = 2;
	int color;
	int menu_len;
	
	menu = menu_new; // set global variable
	
	for(menu_len=0; menu[menu_len].name[0] != '\0'; menu_len++)
		;
	if ( menu_len > MENU_LEN_PARANOIA ) // paranoia check
		return;
	
	werase(window);
	box(window, 0, 0);
	mvwprintw(window, 0, 0, "PROCESSING USER REQUEST");
	
	for(i=0; i < menu_len; i++) {
		
		color = menu[i].color_func != NULL ? menu[i].color_func(&menu[i], menu[i].private) : 3;
		
		wattron(window, COLOR_PAIR(color));
		const int k = strlen(menu[i].name);
		const int pos = (FIELD_WIDTH-k)/2;
		mvwprintw(window, y, x, "%*s%*c", pos+k, menu[i].name, FIELD_WIDTH-(pos+k), ' ');
		wattroff(window, COLOR_PAIR(color));
		wrefresh(window);
		
		// advance
		y+=FIELD_HEIGHT;
		if ( y >= HEIGHT-2 ) {
			x = x+2+FIELD_WIDTH;
			y = 2;
			if ( x >= WIDTH ) {
				break;
			}
		}
	}
	
	box(window, 0, 0);
	mvwprintw(window, 0, 0, "READY");
	mvwprintw(window, HEIGHT-1, 0, "");
	wrefresh(window);
	
	
	// clear all  accidental clicks  when  rendering the menu
	nodelay(window, TRUE);
	wgetch(window);
	nodelay(window, FALSE);
}

void choose_menu(WINDOW *w, int mouse_x, int mouse_y) 
{
	int menu_len, i, x, y;
	
	for(i=0; menu[i].name[0] != '\0'; i++)
		;
	if ( i > MENU_LEN_PARANOIA ) // paranoia check
		return;
	menu_len = i;
	
	for(x=2, y=2, i=0; i < menu_len; i++) {
		const int min_x = x;
		const int max_x = x+FIELD_WIDTH;
		const int min_y = y-1;
		const int max_y = y+1;
		
#define INBOR(x, min, max) (( x >= min )&&(x <= max))
		if ( INBOR(mouse_x, min_x, max_x) && INBOR(mouse_y, min_y, max_y) ) {
#undef INBOR
			break;
		}
		
		// advance
		y+=FIELD_HEIGHT;
		if ( y >= HEIGHT-2 ) {
			x = x+2+FIELD_WIDTH;
			y = 2;
			if ( x >= WIDTH ) {
				break;
			}
		}
	}
		
	if ( i == menu_len )
		return;
	
	werase(window);
	box(window, 0, 0);
	mvwprintw(window, 10, 10, "PROCESSING: click on %s", menu[i].name);
	wrefresh(window);
	
	//run click_function
	if ( menu[i].click_func != NULL )
		(*menu[i].click_func)(&menu[i], menu[i].private);
	
	// new menu
	if ( menu[i].click_menu != NULL ) {
		menu = menu[i].click_menu;
	} else {
		menu = main_menu;
	}
	
	render_menu(w, menu);
}

void init_scr(WINDOW **window)
{	
	/* Initialize curses */
	initscr();
	clear();
	noecho();
	cbreak();	//Line buffering disabled. pass on everything
	start_color();
	use_default_colors();
	
	init_pair(1, COLOR_BLACK, COLOR_RED);
	init_pair(2, COLOR_BLACK, COLOR_GREEN);
	init_pair(3, COLOR_BLACK, COLOR_WHITE);
	init_pair(4, COLOR_WHITE, COLOR_WHITE);

	//attron(A_REVERSE);
	//mvprintw(23, 1, "Click on Exit to quit (Works best in a virtual console)");
	//refresh();
	//attroff(A_REVERSE);

	/* Print the menu for the first time */
	*window = newwin(HEIGHT, WIDTH, 0, 0);
	keypad(*window, TRUE);
	nodelay(*window, FALSE);
	
	/* Get all the mouse events */
	mousemask(ALL_MOUSE_EVENTS, NULL);
}

int main()
{
	int mouse_x, mouse_y;
	
	signal(SIGHUP,sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	alloc_dynamic_menus(PSSCRIPT);
	
	init_scr(&window);
	render_menu(window, main_menu);
	
	while(1)
	{	
		switch( wait_for_mouse_click(window, &mouse_x, &mouse_y) ) {
		case 1: // right mouse click
			choose_menu(window, mouse_x, mouse_y);
			break;
		}
	}
	return 0;
}

